package es

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"mall/common/enumx"
	"mall/common/errorx"
	esmodel "mall/common/esmodel/blog"
	"mall/common/globalkey"
	"mall/common/resultx"
	"mall/service/blog/model"
	"mall/service/blog/rpc/internal/svc"
	"mall/service/blog/rpc/types/blog"

	"github.com/elastic/go-elasticsearch/v7/esapi"
	"github.com/sirupsen/logrus"
	"github.com/zeromicro/go-zero/core/logx"
)

type DeleteEsKBPostLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
	logx.Logger
}

func NewDeleteEsKBPostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteEsKBPostLogic {
	return &DeleteEsKBPostLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

// 删除指定百科  并重新添加所有相关的香水百科
func (l *DeleteEsKBPostLogic) DeleteEsKBPost(in *blog.IDsReq) (*blog.Empty, error) {
	if len(in.Ids) == 0 {
		return &blog.Empty{}, nil
	}
	for _, id := range in.Ids {
		// Set up the request object.
		req := esapi.DeleteRequest{
			Index:      "scentrobe-kb-entry",
			DocumentID: strconv.FormatUint(id, 10),
		}
		res, err := req.Do(l.ctx, l.svcCtx.EsClient)
		if err != nil {
			logrus.Info(fmt.Sprintf("DeleteRequest getting response Error: %+v", err))
			return nil, err
		}

		defer res.Body.Close()

		if res.IsError() {
			logrus.Info(fmt.Sprintf("[%s] ERROR deleting document ID=%s, %+v", res.Status(), id, res))
			return nil, errorx.Status(errorx.ERROR_ELASTIC_DELETE_DOCUMENT_FAILED, "")
		}

		// Deserialize the response into a map.
		var resMap map[string]any
		if err = json.NewDecoder(res.Body).Decode(&resMap); err != nil {
			logrus.Info(fmt.Sprintf("Error parsing the response body: %s", err))
			return nil, err
		}
		logrus.Info(fmt.Sprintf("DeleteEsKBPost resMap: %+v", resMap))
		//查所有有关的香水id
		childPostList, err := l.getEsChildKBPost(id)
		if err != nil {
			return nil, err
		}
		//有相关的香水百科  重新添加
		if len(childPostList) > 0 {
			var cid []any
			for _, v := range childPostList {
				cid = append(cid, v)
			}
			err = l.updateEntriesToEs(cid)
			if err != nil {
				return nil, err
			}
			logrus.Info(fmt.Sprintf("DeleteEsKBPost updateEntriesToEs PostIds: %+v", cid))
		}
	}
	return &blog.Empty{}, nil
}

func (l *DeleteEsKBPostLogic) getEsChildKBPost(parentId uint64) ([]any, error) {
	var buf bytes.Buffer
	var mustConditions []map[string]any
	if parentId > 0 {
		mustConditions = append(mustConditions, map[string]any{
			"multi_match": map[string]any{
				"query": parentId,
				"fields": []string{
					"Brand.Id", "Nose.Id", "Note.Id",
				},
			},
		})
	}

	query := map[string]any{
		"query": map[string]any{
			"bool": map[string]any{
				"must": mustConditions,
			},
		},
	}
	if err := json.NewEncoder(&buf).Encode(query); err != nil {
		logrus.Info(fmt.Sprintf("AdvancedSearch Error encoding query: %+v", err))
		return nil, resultx.NewErrCode(resultx.SERVER_COMMON_ERROR)
	}

	// Perform the search request.
	client := l.svcCtx.EsClient
	if client == nil {
		logrus.Info(fmt.Sprintf("Error getting EsClient"))
		return nil, resultx.NewErrCode(resultx.SERVER_COMMON_ERROR)
	}

	res, err := client.Search(
		client.Search.WithContext(l.ctx),
		client.Search.WithIndex("scentrobe-kb-entry"),
		client.Search.WithBody(&buf),
		client.Search.WithTrackTotalHits(true),
		client.Search.WithPretty(),
	)
	if err != nil {
		logrus.Info(fmt.Sprintf("Error getting response: %+v", err))
		return nil, resultx.NewErrCode(resultx.SERVER_COMMON_ERROR)
	}

	defer res.Body.Close()
	if res.IsError() {
		var e map[string]any
		if err = json.NewDecoder(res.Body).Decode(&e); err != nil {
			logrus.Info(fmt.Sprintf("Error parsing the response body: %+v", err))
		} else {
			// Print the response status and error information.
			logrus.Info(fmt.Sprintf(
				"[%s] %s: %s",
				res.Status(),
				e["error"].(map[string]any)["type"],
				e["error"].(map[string]any)["reason"],
			))
		}

		return nil, resultx.NewErrCode(resultx.SERVER_COMMON_ERROR)
	}
	var rr esmodel.EsEntryModel
	if err := json.NewDecoder(res.Body).Decode(&rr); err != nil {
		logrus.Info(fmt.Sprintf("Error parsing the response body: %+v", err))
		return nil, resultx.NewErrCode(resultx.SERVER_COMMON_ERROR)
	}
	var eid []any
	// 获取有关系的香水id
	for _, v := range rr.Hits.Hits {
		eid = append(eid, v.Source.Id)
	}
	return eid, nil
}

func (l *DeleteEsKBPostLogic) updateEntriesToEs(ids []any) error {
	filterMap := make(map[string]map[string][]any)
	filterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]any{
		"in": ids,
	}
	filterMap[model.BlogKnowledgeBasisColumns.PostStatus] = map[string][]any{
		"equalTo": []any{enumx.PostStatusPublish},
	}
	entryList, _, err := l.svcCtx.BlogBook.GetList(l.ctx, model.M{}, -1, filterMap)
	if err != nil {
		return err
	}
	if len(entryList) == 0 {
		return nil
	}
	var eid []any
	var idEntryMap = map[uint64]struct{}{}
	for _, v := range entryList {
		if _, ok := idEntryMap[v.ID]; !ok {
			idEntryMap[v.ID] = struct{}{}
			eid = append(eid, v.ID)
		}

	}

	termRelFilterMap := make(map[string]map[string][]any)
	termRelFilterMap[model.BlogTermRelationshipColumns.ObjectID] = map[string][]any{
		"in": eid,
	}
	termRelFilterMap[model.BlogTermRelationshipColumns.ObjectType] = map[string][]any{
		"equalTo": {globalkey.PostTypeKnowledgeBase},
	}
	termRelList, _, err := l.svcCtx.BlogTerm.GetRelationshipList(l.ctx, model.M{
		"select": model.BlogTermRelationshipColumns.TermID + "," + model.BlogTermRelationshipColumns.ObjectID,
	}, -1, termRelFilterMap)
	if err != nil {
		return err
	}
	var tid []any
	var idTermRelMap = map[uint64]uint64{}
	if len(termRelList) > 0 {
		for _, v := range termRelList {
			if _, ok := idTermRelMap[v.ObjectID]; !ok {
				idTermRelMap[v.ObjectID] = v.TermID
				tid = append(tid, v.TermID)
			}
		}
	}
	termFilterMap := make(map[string]map[string][]any)
	termFilterMap[model.BlogTermColumns.TermID] = map[string][]any{
		"in": tid,
	}
	termList, _, err := l.svcCtx.BlogTerm.GetList(l.ctx, model.M{
		"select": model.BlogTermColumns.TermID + "," + model.BlogTermColumns.Name + "," + model.BlogTermColumns.Slug,
	}, -1, termFilterMap)
	if err != nil {
		return err
	}
	var idTermMap = map[uint64]*model.BlogTerm{}
	if len(termList) > 0 {
		for _, v := range termList {
			if _, ok := idTermMap[v.TermID]; !ok {
				idTermMap[v.TermID] = v
			}
		}
	}
	entryMetaFilterMap := make(map[string]map[string][]any)
	entryMetaFilterMap[model.BlogKnowledgeBaseMetumColumns.KnowledgeBaseID] = map[string][]any{
		"in": eid,
	}
	entryMetaFilterMap[model.BlogKnowledgeBaseMetumColumns.MetaKey] = map[string][]any{
		"in": {"_year", "_gender"},
	}
	entryMetaList, _, err := l.svcCtx.BlogBook.GetMeta(l.ctx, model.M{
		"select": model.BlogKnowledgeBaseMetumColumns.KnowledgeBaseID + "," + model.BlogKnowledgeBaseMetumColumns.MetaKey + "," + model.BlogKnowledgeBaseMetumColumns.MetaValue,
	}, -1, entryMetaFilterMap)
	if err != nil {
		return err
	}
	var idEntryMetaMap = map[uint64][]*model.BlogKnowledgeBaseMetum{}
	if len(entryMetaList) > 0 {
		for _, v := range entryMetaList {
			idEntryMetaMap[v.KnowledgeBaseID] = append(idEntryMetaMap[v.KnowledgeBaseID], v)
		}
	}
	var res []*esmodel.Entry
	for _, v := range entryList {
		var year uint64
		var gender uint64
		if ms, ok := idEntryMetaMap[v.ID]; ok {
			for _, m := range ms {
				if m.MetaKey == "_year" {
					year, _ = strconv.ParseUint(m.MetaValue.String, 10, 64)
				}
				if m.MetaKey == "_gender" {
					gender, _ = strconv.ParseUint(m.MetaValue.String, 10, 64)
				}
			}
		}
		sectionId := idTermRelMap[v.ID]
		var sections, brand, nose []esmodel.EsEntrySpecialModel
		var note []struct {
			esmodel.EsEntrySpecialModel
			GroupId uint64 `json:"GroupId"`
		}
		sections = append(sections, esmodel.EsEntrySpecialModel{
			Id:    idTermMap[sectionId].TermID,
			Title: idTermMap[sectionId].Name,
			Slug:  idTermMap[sectionId].Slug,
		})
		if sectionId == uint64(globalkey.TermCategoryTypePerfume) {
			entryRelFilterMap := make(map[string]map[string][]any)
			entryRelFilterMap[model.BlogKnowledgeBaseRelationshipColumns.FromEntryID] = map[string][]any{
				"equalTo": {v.ID},
			}
			entryRelFilterMap[model.BlogKnowledgeBaseRelationshipColumns.FromSectionID] = map[string][]any{
				"equalTo": {uint64(globalkey.TermCategoryTypePerfume)},
			}
			entryRelList, _, err := l.svcCtx.BlogBook.GetRelationshipList(l.ctx, model.M{
				"select": model.BlogKnowledgeBaseRelationshipColumns.ToEntryID + "," + model.BlogKnowledgeBaseRelationshipColumns.ToSectionID + "," + model.BlogKnowledgeBaseRelationshipColumns.ToEntryMeta,
			}, -1, entryRelFilterMap)
			if err != nil {
				return err
			}
			if len(entryRelList) > 0 {
				var rid []any
				for _, r := range entryRelList {
					rid = append(rid, r.ToEntryID)
				}
				entryFilterMap := make(map[string]map[string][]any)
				entryFilterMap[model.BlogKnowledgeBasisColumns.ID] = map[string][]any{
					"in": rid,
				}
				entryFilterMap[model.BlogKnowledgeBasisColumns.PostStatus] = map[string][]any{
					"equalTo": []any{enumx.PostStatusPublish},
				}
				eList, _, err := l.svcCtx.BlogBook.GetList(l.ctx, model.M{
					"select": model.BlogKnowledgeBasisColumns.PostTitle + "," + model.BlogKnowledgeBasisColumns.PostName,
				}, -1, entryFilterMap)
				if err != nil {
					return err
				}
				var rEntryMap = map[uint64]*model.BlogKnowledgeBasis{}
				if len(eList) > 0 {
					for _, e := range eList {
						if _, ok := rEntryMap[e.ID]; !ok {
							rEntryMap[e.ID] = e
						}
					}
				}
				for _, r := range entryRelList {
					if r.ToSectionID == uint64(globalkey.TermCategoryTypeBrand) {
						brand = append(brand, esmodel.EsEntrySpecialModel{
							Id:    rEntryMap[r.ToEntryID].ID,
							Title: rEntryMap[r.ToEntryID].PostTitle,
							Slug:  rEntryMap[r.ToEntryID].PostName,
						})
					}
					if r.ToSectionID == uint64(globalkey.TermCategoryTypeNose) {
						nose = append(nose, esmodel.EsEntrySpecialModel{
							Id:    rEntryMap[r.ToEntryID].ID,
							Title: rEntryMap[r.ToEntryID].PostTitle,
							Slug:  rEntryMap[r.ToEntryID].PostName,
						})
					}
					if r.ToSectionID == uint64(globalkey.TermCategoryTypeNote) {
						var group uint64
						if r.ToEntryMeta == "top notes" {
							group = uint64(globalkey.NoteTypeTopNotes)
						}
						if r.ToEntryMeta == "heart notes" {
							group = uint64(globalkey.NoteTypeHeartNotes)
						}
						if r.ToEntryMeta == "base notes" {
							group = uint64(globalkey.NoteTypeBaseNotes)
						}
						if r.ToEntryMeta == "note" {
							group = uint64(globalkey.TermCategoryTypeNose)
						}
						if _, ok := rEntryMap[r.ToEntryID]; !ok {
							continue
						}
						note = append(note, struct {
							esmodel.EsEntrySpecialModel
							GroupId uint64 `json:"GroupId"`
						}{
							EsEntrySpecialModel: esmodel.EsEntrySpecialModel{
								Id:    rEntryMap[r.ToEntryID].ID,
								Title: rEntryMap[r.ToEntryID].PostTitle,
								Slug:  rEntryMap[r.ToEntryID].PostName,
							},
							GroupId: group,
						})
					}
				}
			}
		} else {
			brand = nil
			nose = nil
			nose = nil
		}
		res = append(res, &esmodel.Entry{
			Id:       v.ID,
			Title:    v.PostTitle,
			Slug:     v.PostName,
			Excerpt:  v.PostExcerpt,
			Year:     year,
			Gender:   gender,
			Sections: sections,
			Brand:    brand,
			Nose:     nose,
			Note:     note,
		})
	}
	//添加es
	if len(res) > 0 {
		for _, v := range res {
			//修改kbpost
			err := l.AddEsData(l.ctx, v)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func (l *DeleteEsKBPostLogic) AddEsData(ctx context.Context, entry *esmodel.Entry) error {
	// Build the request body.
	// Marshal Elasticsearch document struct object to JSON string
	docStr, err := l.jsonStruct(entry)
	if err != nil {
		return err
	}

	id := strconv.FormatUint(entry.Id, 10)
	req := esapi.IndexRequest{
		Index:      "scentrobe-kb-entry",
		DocumentID: id,
		Body:       strings.NewReader(docStr),
		Refresh:    "false",
	}

	// Return an API response object from request
	res, err := req.Do(ctx, l.svcCtx.EsClient)
	if err != nil {
		logrus.Info(fmt.Sprintf("IndexRequest getting response ERROR: %+v", err))
		return err
	}
	defer res.Body.Close()

	if res.IsError() {
		logrus.Info(fmt.Sprintf("[%s] ERROR indexing document ID=%s, %+v", res.Status(), id, res))
		return errorx.Status(errorx.ERROR_ELASTIC_DOCUMENT_FAILED, "")
	}

	// Deserialize the response into a map.
	var resMap map[string]any
	if err = json.NewDecoder(res.Body).Decode(&resMap); err != nil {
		logrus.Info(fmt.Sprintf("Error parsing the response body: %s", err))
		return err
	}

	// logrus.Info(fmt.Sprintf("\nIndexRequest() RESPONSE:"))
	// // Print the response status and indexed document version.
	// logrus.Info(fmt.Sprintf("Status: %v", res.Status()))
	// logrus.Info(fmt.Sprintf("Result: %v", resMap["result"]))
	// logrus.Info(fmt.Sprintf("Version: %d", int(resMap["_version"].(float64))))
	logrus.Info(fmt.Sprintf("resMap: %+v", resMap))
	// logrus.Info(fmt.Sprintf("\n"))

	return nil
}

// A function for marshaling structs to JSON string
func (l *DeleteEsKBPostLogic) jsonStruct(entry *esmodel.Entry) (string, error) {
	// logrus.Info(fmt.Sprintf("jsonStruct docStruct: %+v", entry))

	// Marshal the struct to JSON and check for errors
	b, err := json.Marshal(entry)
	if err != nil {
		logrus.Info(fmt.Sprintf("AddEsData jsonStruct json.Marshal ERROR: %+v", err))
		return "", err
	}

	return string(b), nil
}
